/*
 * Copyright (C) 2008-2011 CBDP Consortium
 * 
 * All rights reserved.
 *
 * This software is licensed as described in the file COPYING, which
 * you should have received as part of this distribution.
 * 
 * This software consists of contributions made by many individuals, 
 * listed below:
 *
 * Author: 	Xabier Laiseca <xabier.laiseca@deusto.es>
 * 			David Ausín <david.ausin@deusto.es>
 */
package cbdp.server.contextreasoner.impl.contextmanager.online.model;

import static cbdp.server.contextreasoner.impl.contextmanager.online.model.Checker.checkNullParameter;
import static cbdp.server.contextreasoner.impl.contextmanager.online.model.Checker.checkNullProperty;
import static cbdp.server.contextreasoner.impl.contextmanager.online.model.Checker.checkNullResource;
import static cbdp.server.contextreasoner.impl.contextmanager.online.model.Checker.checkValidValue;

import java.util.Collection;
import java.util.Vector;

import cbdp.server.contextreasoner.model.GenericProperty;
import cbdp.server.contextreasoner.model.Instance;
import cbdp.server.contextreasoner.model.ObjectDatatypeProperty;
import cbdp.server.contextreasoner.model.PrimitiveDatatypeProperty;
import cbdp.server.contextreasoner.model.exceptions.InvalidDatatypeException;
import cbdp.server.contextreasoner.model.exceptions.NotExistingInstanceException;
import cbdp.server.contextreasoner.model.exceptions.NotExistingPropertyException;
import cbdp.server.contextreasoner.model.exceptions.NotExistingPropertyInstanceException;
import cbdp.server.contextreasoner.model.exceptions.PreviouslyDeteledPropertyException;

import com.hp.hpl.jena.ontology.DatatypeProperty;
import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.ObjectProperty;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.shared.Lock;

/**
 * An eager saving implementation of <code>Instance</code>. 
 * @author   Xabier Laiseca (University of Deusto)
 */
public class EagerSavingInstance implements Instance
{
	private OntModel ontModel;
	private Individual individual;
	private String id;
	
	private Individual getIndividual()
	{
		if(individual == null)
		{
			individual = ontModel.getIndividual(id);
		}
		
		return individual;
	}
	
	/**
	 * @param ontModel
	 * @uml.property  name="ontModel"
	 */
	public void setOntModel(final OntModel ontModel)
	{
		this.ontModel = ontModel;
	}
	
	/**
     * @param individual
     * @uml.property  name="individual"
     */
	public void setIndividual(final Individual individual)
	{
		this.individual = individual;
	}
	
	public void setIdentifier(final String id)
	{
		this.id = id;
	}
	
	@Override
	public void addPrimitiveDatatypeProperty(final String name, final boolean value) throws NotExistingPropertyException, InvalidDatatypeException
	{		
		addGenericPrimitiveDatatypeProperty(name, value);
	}

	@Override
	public void addPrimitiveDatatypeProperty(final String name, final char value) throws NotExistingPropertyException, InvalidDatatypeException
	{
		addGenericPrimitiveDatatypeProperty(name, value);
	}

	@Override
	public void addPrimitiveDatatypeProperty(final String name, final double value) throws NotExistingPropertyException, InvalidDatatypeException
	{
		addGenericPrimitiveDatatypeProperty(name, value);
	}

	@Override
	public void addPrimitiveDatatypeProperty(final String name, final float value) throws NotExistingPropertyException, InvalidDatatypeException
	{
		addGenericPrimitiveDatatypeProperty(name, value);
	}

	@Override
	public void addPrimitiveDatatypeProperty(final String name, final int value) throws NotExistingPropertyException, InvalidDatatypeException
	{
		addGenericPrimitiveDatatypeProperty(name, value);
	}

	@Override
	public void addPrimitiveDatatypeProperty(final String name, final long value) throws NotExistingPropertyException, InvalidDatatypeException
	{
		addGenericPrimitiveDatatypeProperty(name, value);
	}

	@Override
	public void addPrimitiveDatatypeProperty(final String name, final String value) throws NotExistingPropertyException, InvalidDatatypeException
	{
		addGenericPrimitiveDatatypeProperty(name, value);
	}
	
	private void addGenericPrimitiveDatatypeProperty(final String name, final Object value) throws NotExistingPropertyException, InvalidDatatypeException
	{
		checkNullParameter(name, "name");
		checkNullParameter(value, "value");
		
		ontModel.enterCriticalSection(Lock.WRITE);
		
		try
		{
			final DatatypeProperty datatypeProperty = ontModel.getDatatypeProperty(name);
			checkNullProperty(datatypeProperty);
			
			final Literal literal = ontModel.createTypedLiteral(value);
			
			checkValidValue(literal, datatypeProperty);
			
			getIndividual().addProperty(datatypeProperty, literal);
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	@Override
	public void addObjectDatatypeProperty(final String name, final String objectName) throws NotExistingInstanceException, NotExistingPropertyException
	{
		checkNullParameter(name, "name");
		checkNullParameter(objectName, "objectName");
	
		ontModel.enterCriticalSection(Lock.WRITE);
		
		try
		{
			final ObjectProperty property = ontModel.getObjectProperty(name);
			checkNullProperty(property);
			
			//final Individual referencedIndividual = ontModel.getIndividual(objectName);
			final OntResource resource = ontModel.getOntResource(objectName);
			checkNullResource(resource);
			
			getIndividual().addProperty(property, resource);			
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	@Override
	public void deleteProperty(final GenericProperty genericProperty) throws NotExistingPropertyException, PreviouslyDeteledPropertyException
	{
		checkNullParameter(genericProperty, "genericProperty");
		
		genericProperty.delete();
	}
	
	@Override
	public String getIdentifier()
	{
		return getIndividual().getURI();
	}

	private final static String PROPERTY_INSTANCE_NOT_FOUND_MESSAGE 		= "The property does not exists";
	private final static String PROPERTY_NOT_OBJECT_MESSAGE 	= "The property is not a object";
	private final static String PROPERTY_NOT_PRIMITIVE_DATATYPE_MESSAGE 	= "The property is not a primitive datatype";
	private final static String INCORRECT_DATA_TYPE_MESSAGE 	= "The type of the property is not correct";
	
	
	

	@Override
	public ObjectDatatypeProperty getObjectDatatypeProperty(final String name) throws NotExistingPropertyException, NotExistingPropertyInstanceException
	{
		checkNullParameter(name, "name");
		
		ontModel.enterCriticalSection(Lock.READ);
		
		try
		{
			final ObjectProperty objectProperty = ontModel.getObjectProperty(name);
			checkNullProperty(objectProperty);
			
			return loadObjectDatatypeProperty(getIndividual().getProperty(objectProperty));			
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	@Override
	public PrimitiveDatatypeProperty getPrimitiveDatatypeProperty(final String name) throws NotExistingPropertyException, NotExistingPropertyInstanceException
	{
		checkNullParameter(name, "name");
		
		ontModel.enterCriticalSection(Lock.READ);
		
		try
		{
			final DatatypeProperty datatypeProperty = ontModel.getDatatypeProperty(name);
			checkNullProperty(datatypeProperty);
			
			return  loadDatatypeProperty(new EagerSavingPrimitiveDatatypeProperty(), datatypeProperty);			
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	

	
	private EagerSavingPrimitiveDatatypeProperty loadDatatypeProperty(final EagerSavingPrimitiveDatatypeProperty genericProperty, final Property property) throws NotExistingPropertyInstanceException
	{
		ontModel.enterCriticalSection(Lock.READ);
		
		try
		{
			final Statement statement = getIndividual().getProperty(property);
			
			return loadPrimitiveDatatypeProperty(statement);
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	private EagerSavingObjectDatatypeProperty loadObjectDatatypeProperty(final Statement statement) throws NotExistingPropertyInstanceException
	{
		final EagerSavingObjectDatatypeProperty objectProperty = new EagerSavingObjectDatatypeProperty();
		
		if (statement != null)
		{
			final RDFNode rdfNode = statement.getObject();
			
			if (rdfNode != null)
			{
				if (rdfNode.canAs(Individual.class))
				{
					objectProperty.setStatement(statement);
					objectProperty.setOntModel(ontModel);
					
					return objectProperty;
				}
				else throw new NotExistingPropertyInstanceException(PROPERTY_NOT_OBJECT_MESSAGE);
			}
			else throw new NotExistingPropertyInstanceException(PROPERTY_INSTANCE_NOT_FOUND_MESSAGE);
		}
		else throw new NotExistingPropertyInstanceException(PROPERTY_INSTANCE_NOT_FOUND_MESSAGE);
	}
	
	private EagerSavingPrimitiveDatatypeProperty loadPrimitiveDatatypeProperty(final Statement statement) throws NotExistingPropertyInstanceException
	{
		final EagerSavingPrimitiveDatatypeProperty primitiveProperty = new EagerSavingPrimitiveDatatypeProperty();
		
		if (statement != null)
		{
			final RDFNode rdfNode = statement.getObject();
			
			if (rdfNode != null)
			{
				if (rdfNode.canAs(Literal.class))
				{
					primitiveProperty.setStatement(statement);
					primitiveProperty.setOntModel(ontModel);
					
					return primitiveProperty;
				}
				else throw new NotExistingPropertyInstanceException(PROPERTY_NOT_PRIMITIVE_DATATYPE_MESSAGE);
			}
			else throw new NotExistingPropertyInstanceException(PROPERTY_INSTANCE_NOT_FOUND_MESSAGE);
		}
		else throw new NotExistingPropertyInstanceException(PROPERTY_INSTANCE_NOT_FOUND_MESSAGE);
	}
	
	
	@Override
	public Collection<ObjectDatatypeProperty> getObjectDatatypeProperties(final String name) throws NotExistingPropertyException
	{
		final StmtIterator iterator = listObjectProperties(name);
		
		final Vector<ObjectDatatypeProperty> values = new Vector<ObjectDatatypeProperty>(); 
		
		while(iterator.hasNext())
		{
			final Statement statement = iterator.next();
			
			try
			{
				final ObjectDatatypeProperty primitiveDatatypeProperty = loadObjectDatatypeProperty(statement);
				values.add(primitiveDatatypeProperty);
			}
			catch (final NotExistingPropertyInstanceException e)
			{
				e.printStackTrace();
			}
		}
		
		return values;
	}
	
	@Override
	public Collection<PrimitiveDatatypeProperty> getPrimitiveDatatypeProperties(final String name) throws NotExistingPropertyException
	{
		final StmtIterator iterator = listPrimitiveProperties(name);
		
		final Vector<PrimitiveDatatypeProperty> values = new Vector<PrimitiveDatatypeProperty>(); 
		
		while(iterator.hasNext())
		{
			final Statement statement = iterator.next();
			
			try
			{
				final PrimitiveDatatypeProperty primitiveDatatypeProperty = loadPrimitiveDatatypeProperty(statement);
				values.add(primitiveDatatypeProperty);
			}
			catch (final NotExistingPropertyInstanceException e)
			{
				e.printStackTrace();
			}
		}
		
		return values;
	}
	
	private StmtIterator listPrimitiveProperties(final String name) throws NotExistingPropertyException
	{
		checkNullParameter(name, "name");
		
		ontModel.enterCriticalSection(Lock.READ);
		
		try
		{
			final DatatypeProperty property = ontModel.getDatatypeProperty(name);
			checkNullProperty(property);
			
			return getIndividual().listProperties(property);			
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	private StmtIterator listObjectProperties(final String name) throws NotExistingPropertyException
	{
		checkNullParameter(name, "name");
		
		ontModel.enterCriticalSection(Lock.READ);
		
		try
		{
			final ObjectProperty property = ontModel.getObjectProperty(name);
			checkNullProperty(property);
			
			return getIndividual().listProperties(property);
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	private <T> T getLiteralPropertyValue(final RDFNode rdfNode, final Class<T> clazz) throws NotExistingPropertyInstanceException, InvalidDatatypeException
	{
		if (rdfNode != null)
		{
			if (rdfNode.canAs(Literal.class))
			{
				final Literal literal = rdfNode.as(Literal.class);
				
				try
				{
					return clazz.cast(literal.getValue());
				}
				catch (ClassCastException e)
				{
					throw new InvalidDatatypeException(INCORRECT_DATA_TYPE_MESSAGE, e);
				}
			}
			else throw new NotExistingPropertyInstanceException(PROPERTY_NOT_PRIMITIVE_DATATYPE_MESSAGE);
		}
		else throw new NotExistingPropertyInstanceException(PROPERTY_INSTANCE_NOT_FOUND_MESSAGE);
	}

	@SuppressWarnings("unused")
	private <T> Collection<T> getPrimitiveDatatypePropertyValues(final String name, final Class<T> clazz) throws NotExistingPropertyException, InvalidDatatypeException
	{
		checkNullParameter(name, "name");
		
		ontModel.enterCriticalSection(Lock.READ);
		
		try
		{
			final DatatypeProperty datatypeProperty = ontModel.getDatatypeProperty(name);
			checkNullProperty(datatypeProperty);
			
			final NodeIterator iterator = getIndividual().listPropertyValues(datatypeProperty);
			
			final Vector<T> values = new Vector<T>(); 
			
			while(iterator.hasNext())
			{
				final RDFNode rdfNode = iterator.next();
				
				try
				{
					values.add(getLiteralPropertyValue(rdfNode, clazz));
				}
				catch (NotExistingPropertyInstanceException e) {}
			}
			
			return values;
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	@SuppressWarnings("unused")
	private <T> T getPrimitiveDatatypePropertyValue(final String name, final Class<T> clazz) throws NotExistingPropertyException, NotExistingPropertyInstanceException, InvalidDatatypeException
	{
		checkNullParameter(name, "name");
		
		ontModel.enterCriticalSection(Lock.READ);
		
		try
		{
			final DatatypeProperty datatypeProperty = ontModel.getDatatypeProperty(name);
			checkNullProperty(datatypeProperty);
			
			final RDFNode rdfNode = getIndividual().getPropertyValue(datatypeProperty);
			
			return getLiteralPropertyValue(rdfNode, clazz);
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}
	
	@Override
	public void deleteAllProperties(final String name) throws NotExistingPropertyException
	{
		checkNullParameter(name, "name");
		
		ontModel.enterCriticalSection(Lock.WRITE);
		
		try
		{
			final Property property = ontModel.getOntProperty(name);
			checkNullProperty(property);
			
			getIndividual().removeAll(property);
		}
		finally
		{
			ontModel.leaveCriticalSection();
		}
	}

	@Override
	public String getOntologyClass()
	{
		return getIndividual().getOntClass().getURI();
	}
}
